﻿/* 2014/11/9 */
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Cosmos.IO.FileSystemPath.Searcher
{
    /// <summary>
    /// Abstract class.
    /// Provides base implement for searching file or directory
    /// by file system search function.
    /// </summary>
    public abstract class SystemApiPathSearcher : IPathSearcher
    {
        private bool _isPatternNullOrEmpty;

        public virtual string[] Search(string searchPattern, string directoryPath,
            SearchFileDirectoryOption searchFileDirectoryOption, bool invertMatch)
        {
            _isPatternNullOrEmpty = String.IsNullOrEmpty(searchPattern);

            DirectoryInfo directory = new DirectoryInfo(directoryPath);
            List<string> pathList = new List<string>();

            bool containDirectory = false;
            bool containFile = false;
            switch (searchFileDirectoryOption)
            {
                case SearchFileDirectoryOption.None:
                    containFile = false;
                    containDirectory = false;
                    break;
                case SearchFileDirectoryOption.File:
                    containFile = true;
                    containDirectory = false;
                    break;
                case SearchFileDirectoryOption.Directory:
                    containFile = false;
                    containDirectory = true;
                    break;
                case SearchFileDirectoryOption.All:
                    containFile = true;
                    containDirectory = true;
                    break;
                default:
                    containFile = true;
                    containDirectory = false;
                    break;
            }

            if (_isPatternNullOrEmpty)
            {
                /*
                 * When pattern is nothing, searches all paths
                 * no matter whether invert match or not.
                 */
                FileInfo[] allFiles;
                DirectoryInfo[] allDirectories;
                SearchAllPath(directory, out allFiles, out allDirectories,
                    containDirectory, containFile);

                if (containDirectory)
                {
                    AddPathToList(allDirectories, pathList);
                }
                if (containFile)
                {
                    AddPathToList(allFiles, pathList);
                }
            }
            else
            {
                /*
                 * Searches paths by pattern.
                 */
                searchPattern = GenerateSearchPattern(searchPattern);

                if (!invertMatch)
                {
                    FileInfo[] matchedFiles;
                    DirectoryInfo[] matchedDirectories;
                    SearchMatchedPath(directory, searchPattern,
                        out matchedFiles, out matchedDirectories,
                        containDirectory, containFile);

                    if (containDirectory)
                    {
                        AddPathToList(matchedDirectories, pathList);
                    }
                    if (containFile)
                    {
                        AddPathToList(matchedFiles, pathList);
                    }
                }
                else
                {
                    /*
                     * Invert match will remove matched paths
                     * from all paths.
                     */
                    FileInfo[] allFiles;
                    DirectoryInfo[] allDirectories;
                    FileInfo[] matchedFiles;
                    DirectoryInfo[] matchedDirectories;
                    SearchAllPath(directory, out allFiles, out allDirectories,
                        containDirectory, containFile);
                    SearchMatchedPath(directory, searchPattern,
                        out matchedFiles, out matchedDirectories,
                        containDirectory, containFile);

                    if (containDirectory)
                    {
                        AddMismatchedPathToList(allDirectories, matchedDirectories,
                            pathList);
                    }
                    if (containFile)
                    {
                        AddMismatchedPathToList(allFiles, matchedFiles, pathList);
                    }                    
                }
            }

            return pathList.ToArray();
        }

        /// <summary>
        /// Generates real search pattern passed to system API.
        /// </summary>
        /// <param name="searchPattern">Original search pattern.</param>
        /// <returns>Real search pattern.</returns>
        protected abstract string GenerateSearchPattern(string searchPattern);

        private void SearchAllPath(DirectoryInfo directory, out FileInfo[] allFiles,
            out DirectoryInfo[] allDirectories, bool containDirectory, bool containFile)
        {
            allFiles = null;
            allDirectories = null;
            if (containDirectory)
            {
                allDirectories = directory.GetDirectories();
            }
            if (containFile)
            {
                allFiles = directory.GetFiles();
            }            
        }

        private void SearchMatchedPath(DirectoryInfo directory, string searchPattern,
            out FileInfo[] matchedFiles, out DirectoryInfo[] matchedDirectories,
            bool containDirectory, bool containFile)
        {
            matchedFiles = null;
            matchedDirectories = null;
            if (containDirectory)
            {
                matchedDirectories = directory.GetDirectories(searchPattern);
            }
            if (containFile)
            {
                matchedFiles = directory.GetFiles(searchPattern);
            }
        }

        private void AddPathToList(FileSystemInfo[] infos, List<string> pathList)
        {
            pathList.Capacity = Math.Max(pathList.Capacity, pathList.Count + infos.Length);
            foreach (FileSystemInfo anInfo in infos)
            {
                pathList.Add(anInfo.FullName);
            }
        }

        private void AddMismatchedPathToList(FileSystemInfo[] allInfos,FileSystemInfo[] matchedInfo
            , List<string> pathList)
        {
            pathList.Capacity = Math.Max(pathList.Capacity,
                pathList.Count + allInfos.Length - matchedInfo.Length);
            foreach (FileSystemInfo anInfo in allInfos)
            {
                if (!matchedInfo.Contains(anInfo))
                {
                    pathList.Add(anInfo.FullName);
                }
            }
        }

        protected bool IsPatternNullOrEmpty
        {
            get
            {
                return _isPatternNullOrEmpty;
            }
        }
    }
}
